package practica3b;

import java.util.Random;

public class Arbol {
	private String dato;
	private boolean valor;
	private Arbol padre;
	private int numHijo;
	private Arbol hijo1, hijo2, hijo3;
	private int altura;
	
	public enum tipoLlenado {CRECIENTE, COMPLETO, RAMPED};
	
	public Arbol(Arbol padre, String dato){
		if(padre!=null){
			this.padre = padre;
			altura = padre.getAltura()+1;
			numHijo = padre.setHijo(this);
		}else{
			altura = 0;
			padre = null;
			numHijo = 0;
		}
		hijo1 = hijo2 = hijo3 = null;
		this.dato = dato;
	}
	
	public Arbol(Arbol a, int numHijo){
		this.padre = a.padre;
		altura = a.altura;
		if(a.hijo1!=null)
			hijo1 = new Arbol(a.hijo1, a.hijo1.getNumHijo());
		if(a.hijo2!=null)
			hijo2 = new Arbol(a.hijo2, a.hijo2.getNumHijo());
		if(a.hijo3!=null)
			hijo3 = new Arbol(a.hijo3, a.hijo3.getNumHijo());
		this.dato = a.dato;
		this.valor = a.valor;
		this.numHijo = numHijo;
	}
	
//	public void copiar(Arbol a){
//		this.padre = a.padre;
//		altura = a.altura;
//		if(a.hijo1!=null)
//			hijo1.copiar(a.hijo1);
//		if(a.hijo2!=null)
//			hijo2.copiar(a.hijo2);
//		if(a.hijo3!=null)
//			hijo3.copiar(a.hijo3);
//		this.dato = a.dato;
//		this.valor = a.valor;
//		this.numHijo = a.numHijo;
//		this.profundidad = a.profundidad;
//	}
	
	public boolean datoEsTerminal(){
		return this.dato.equals("D0") || this.dato.equals("D1") || this.dato.equals("D2") 
				|| this.dato.equals("D3") || this.dato.equals("D4") || this.dato.equals("D5") 
				|| this.dato.equals("D6") || this.dato.equals("D7") || this.dato.equals("A0") 
				|| this.dato.equals("A1") || this.dato.equals("A2");
	}
	
	public boolean datoEsTerminalD(){
		return this.dato.equals("D0") || this.dato.equals("D1") || this.dato.equals("D2") 
				|| this.dato.equals("D3") || this.dato.equals("D4") || this.dato.equals("D5") 
				|| this.dato.equals("D6") || this.dato.equals("D7");
	}
	
	public boolean datoEsFuncion(){
		return this.dato.equals("NOT") || this.dato.equals("OR") 
				|| this.dato.equals("AND") || this.dato.equals("IF"); 
	}
	
	
	public Arbol getPuntoCrucePadre(int altura_cruce){
		if(altura_cruce==0 || datoEsFuncion())//|| this.dato.equals("D0") || this.dato.equals("D1") || this.dato.equals("D2") || this.dato.equals("D3") || this.dato.equals("A0") || this.dato.equals("A1"))
			return this; //return padre;
		else{
			Random r = new Random();
			int hijoCruce; 
			switch(dato){
			case "IF":
				hijoCruce = r.nextInt(3);
				break;
			case "AND": case "OR":
				hijoCruce = r.nextInt(2);
				break;
			case "NOT": default: hijoCruce = 0;
			}
			switch(hijoCruce){
			case 2: 
				return hijo3.getPuntoCrucePadre(altura_cruce-1);
			case 1:
				return hijo2.getPuntoCrucePadre(altura_cruce-1);
			case 0: default:
				return hijo1.getPuntoCrucePadre(altura_cruce-1);
			}
		}
		
	}
	
	public int getHijoRandom(){
		Random r = new Random();
		int hijo;
		switch(dato){
		case "IF":
			hijo = r.nextInt(3);
			break;
		case "AND": case "OR":
			hijo = r.nextInt(2);
			break;
		case "NOT": default: hijo = 0;
		}
		return hijo+1;
	}
	
	public void setHijo(int i, Arbol a){
		switch(i){
		case 3: 
//			hijo3.copiar(a);
//			hijo3 = new Arbol(a,i);
			hijo3 = a;
			hijo3.setPadre(this);
			hijo3.setNumHijo(i);
			hijo3.recalcularAltura();
			break;
		case 2: 
//			hijo2.copiar(a);
//			hijo2 = new Arbol(a,i);
			hijo2 = a;
			hijo2.setPadre(this);
			hijo2.setNumHijo(i);
			hijo2.recalcularAltura();
			break;
		case 1: default:
//			hijo1.copiar(a);
//			hijo1 = new Arbol(a,i);
			hijo1 = a;
			hijo1.setPadre(this);
			hijo1.setNumHijo(i);
			hijo1.recalcularAltura();			
		}
	}
	
	private void recalcularAltura(){
		this.altura = padre.altura+1;
		if(this.hijo1!=null)
			hijo1.recalcularAltura();
		if(this.hijo2!=null)
			hijo2.recalcularAltura();
		if(this.hijo3!=null)
			hijo3.recalcularAltura();
	}
	
	public Arbol getHijo(int i){
		switch(i){
		case 3: 
			return hijo3;
		case 2: 
			return hijo2;
		case 1: default:
			return hijo1;
		}
	}
	
	public void rellenar(tipoLlenado tipo, int altura_max){
		while(insertarDato(tipo, altura_max)){}
	}
	
	public boolean insertarDato(tipoLlenado tipo, int alturaMax){
		Random r = new Random();
		String dato=null;
		int val=0;
		if(altura<alturaMax-2){
			switch(tipo){
				case CRECIENTE:
					val = r.nextInt(15);
					break;
				case COMPLETO:
					val = r.nextInt(4);
					break;
				case RAMPED: default:
					return false;
					//TODO: ???????????????????????????????????
			}
		}else if(altura==alturaMax-2){
			val = r.nextInt(11)+4;
		}else
			return false;
		switch(val){
			case 0:
				dato = "AND";
				break;
			case 1:
				dato = "OR";
				break;
			case 2:
				dato = "NOT";
				break;
			case 3:
				dato = "IF";
				break;
			case 4:
				dato = "D0";
				break;
			case 5:
				dato = "D1";
				break;
			case 6:
				dato = "D2";
				break;
			case 7:
				dato = "D3";
				break;
			case 8:
				dato = "D4";
				break;
			case 9:
				dato = "D5";
				break;
			case 10:
				dato = "D6";
				break;
			case 11:
				dato = "D7";
				break;
			case 12:
				dato = "A0";
				break;
			case 13:
				dato = "A1";
				break;
			case 14: default:
				dato = "A2";
		}
		return insertarDato(dato, tipo, alturaMax);
	}
	
	
	
	private boolean insertarDato(String dato, tipoLlenado tipo, int alturaMax){
		if(datoEsTerminal())//this.dato.equals("D0") || this.dato.equals("D1") || this.dato.equals("D2") || this.dato.equals("D3") || this.dato.equals("A0") || this.dato.equals("A1"))
			return false;
		if(hijo1 == null){
			Random r = new Random();
			if(this.dato=="NOT" && dato=="NOT"){
				switch(r.nextInt(3)){
				case 0:
					dato = "AND";
					break;
				case 1:
					dato = "OR";
					break;
				case 2: default:
					dato = "IF";
				}
			}else if(this.dato=="IF" && datoEsTerminalD()){
				switch(r.nextInt(3)){
				case 0:
					dato = "A2";
					break;
				case 1:
					dato = "A1";
					break;
				case 2: default:
					dato = "A0";
				}
			}
			hijo1 = new Arbol(this,dato);
			return true;
		}else{
			if(!hijo1.insertarDato(tipo,alturaMax)){
				if(this.dato.equals("NOT"))
					return false;
				else if(hijo2!=null){
					if(!hijo2.insertarDato(tipo,alturaMax)){
						if(this.dato.equals("AND") || this.dato.equals("OR"))
							return false;
						else if(hijo3!=null)
							return hijo3.insertarDato(tipo,alturaMax);
						else{
							hijo3 = new Arbol(this,dato);
							return true;
						}
					}else
						return true;
				}else{
					Random r = new Random();
					while(hijo1.dato.equals(dato)){
						switch(r.nextInt(11)){
						case 0:	dato = "D0"; break;
						case 1:	dato = "D1"; break;
						case 2:	dato = "D2"; break;
						case 3:	dato = "D3"; break;
						case 4:	dato = "D4";break;
						case 5:	dato = "D5";break;
						case 6: dato = "D6";break;
						case 7:	dato = "D7";break;
						case 8:	dato = "A0";break;
						case 9:	dato = "A1";break;
						case 10: default: dato = "A2";
						}
					}
					hijo2 = new Arbol(this,dato);
					return true;
				}
			}else
				return true;
		}
	}
	
	private int setHijo(Arbol hijo){
		if(hijo1 == null){
			hijo1 = hijo;
			return 1;
		}else if(hijo2 == null){
			hijo2 = hijo;
			return 2;
		}else{
			hijo3 = hijo;
			return 3;
		}
	}
	
	public void setDato(String dato){
		this.dato= dato; 
	}
	
	public String getDato(){
		return dato;
	}

	public Arbol getPadre() {
		return padre;
	}

	public void setPadre(Arbol padre) {
		this.padre = padre;
	}

	public int getAltura() {
		return altura;
	}

	public String toString(){
		String s = dato;
		if(dato == "OR" || dato == "AND" || dato == "NOT" || dato == "IF"){
			s += "(";
			if(hijo1!=null)
				s += hijo1.toString() + " ";
			if(dato == "OR" || dato == "AND" || dato == "IF"){
				if(hijo2!=null)
					s += hijo2.toString() + " ";
				if(dato == "IF"){
					if(hijo3!=null)
						s += hijo3.toString() + " ";
				}
			}
			s += ")";
		}
		return s;
	}
	
	public String toStringValues(){
		if(dato == "OR" || dato == "AND" || dato == "NOT" || dato == "IF"){
			String s =  dato + "( ";
			if(hijo1!=null)
				s += hijo1.toStringValues() + " ";
			if(dato == "OR" || dato == "AND" || dato == "IF"){
				if(hijo2!=null)
					s += hijo2.toStringValues() + " ";
				if(dato == "IF"){
					if(hijo3!=null)
						s += hijo3.toStringValues() + " ";
				}
			}
			s += ")";
			return s;
		}else if(valor){
			return "1 ";
		}else
			return "0 ";
	}
	
//	private int tipoDato(){
//		switch(dato){
//		case "AND": case "OR": case "IF" : case "NOT": 
//			return 0;
//		case "A0": case "A1": case "D0": case "D1": case "D2": case "D3": 
//			return 1;
//		default:
//			return -1;
//		}
//	}
	
	public boolean evaluar(boolean A0, boolean A1, boolean A2, boolean[] D){
		boolean valHijo2,valHijo3;
		switch(dato){
		case "AND": 
			if(hijo1!=null && hijo2!=null){
				valor = hijo1.evaluar(A0,A1,A2,D) && hijo2.evaluar(A0,A1,A2,D);
				return valor;
			}else
				return false;
		case "OR": 
			if(hijo1!=null && hijo2!=null){
				valor = hijo1.evaluar(A0,A1,A2,D) || hijo2.evaluar(A0,A1,A2,D);
				return valor;
			}else
				return false;
		case "IF" : 
			if(hijo1!=null && hijo2!=null && hijo3!=null){
				valHijo2 = hijo2.evaluar(A0,A1,A2,D);
				valHijo3 = hijo3.evaluar(A0,A1,A2,D);
				if(hijo1.evaluar(A0,A1,A2,D))
					valor = valHijo2;
				else
					valor = valHijo3;
				return valor;
			}else
				return false;
		case "NOT":
			if(hijo1!=null){
				valor = !hijo1.evaluar(A0,A1,A2,D);
				return valor;
			}else
				return false;
		case "A0":
			valor=A0;
			return valor;
		case "A1": 
			valor=A1;
			return valor;
		case "A2": 
			valor=A2;
			return valor;
		case "D0":  
			valor=D[0];
			return valor;
		case "D1":  
			valor=D[1];
			return valor;
		case "D2":  
			valor=D[2];
			return valor;
		case "D3":
			valor=D[3];
			return valor;
		case "D4":
			valor=D[4];
			return valor;
		case "D5":
			valor=D[5];
			return valor;
		case "D6":
			valor=D[6];
			return valor;
		case "D7":
			valor=D[7];
			return valor;
		}
		return false;
	}
	
	public void cambiarHojaRandom(){
		Random r = new Random();
		int val;
		switch(dato){
		case "A0": case "A1": case "A2": case "D0": case "D1": case "D2": case "D3": case "D4": case "D5": case "D6": case "D7": 
			switch(r.nextInt(11)){
			case 0:	dato = "A0"; break;
			case 1:	dato = "A1"; break;
			case 2:	dato = "A2"; break;
			case 3:	dato = "D0"; break;
			case 4:	dato = "D1"; break;
			case 5:	dato = "D2"; break;
			case 6:	dato = "D3"; break;
			case 7:	dato = "D4"; break;
			case 8:	dato = "D5"; break;
			case 9:	dato = "D6"; break;
			case 10:	default: dato = "D7"; break;
			}
			break;
		case "NOT": hijo1.cambiarHojaRandom(); break;
		case "AND": case "OR":  
			switch(r.nextInt(2)){
			case 1: hijo2.cambiarHojaRandom(); break;
			case 0: default: hijo1.cambiarHojaRandom(); break;
			}
			break;
		case "IF" :
			switch(r.nextInt(3)){
			case 2: hijo3.cambiarHojaRandom(); break;
			case 1: hijo2.cambiarHojaRandom(); break;
			case 0: default: hijo1.cambiarHojaRandom(); break;
			}
			break;
		}
	}
	
	public boolean cambiarNodoRandom(){
		Random r = new Random();
		if(datoEsTerminal() || this.dato.equals("NOT"))//this.dato.equals("D0") || this.dato.equals("D1") || this.dato.equals("D2") || this.dato.equals("D3") || this.dato.equals("A0") || this.dato.equals("A1") || this.dato.equals("NOT"))
			return false;
		else if(r.nextBoolean()){ //aleatoriamente: cambia el propio dato, o bien transmite el cambio a sus hijos
			switch(r.nextInt(2)){
			case 1: this.dato="AND"; break;
			case 0: default: this.dato="OR";
			}
			hijo3 = null;
			return true;
		}else if(this.dato.equals("AND") || this.dato.equals("OR")){
				if(r.nextBoolean())
					if(hijo1.cambiarNodoRandom())
						return true;
				return hijo2.cambiarNodoRandom();
		}else{ // dato == "IF"
			switch(r.nextInt(3)){
			case 0: 
				if(hijo1.cambiarNodoRandom())
					return true;
			case 1:
				if(hijo2.cambiarNodoRandom())
					return true;
			case 2:
				if(hijo3.cambiarNodoRandom())
					return true;
			}
			return false;
		}
	}
	
	public boolean cambiarArbolRandom(tipoLlenado tipo, int altura_max){
		Random r = new Random();
		if(datoEsTerminal())//this.dato.equals("D0") || this.dato.equals("D1") || this.dato.equals("D2") || this.dato.equals("D3") || this.dato.equals("A0") || this.dato.equals("A1"))
			return false;
		else if(r.nextBoolean()){ //aleatoriamente: cambia el propio dato, o bien transmite el cambio a sus hijos
			switch(r.nextInt(4)){
			case 3: this.dato="IF";	break;
			case 2:	this.dato="AND"; break;
			case 1: this.dato="OR"; break;
			case 0: default: this.dato="NOT"; 
			}
			hijo1 = hijo2 = hijo3 = null;
			rellenar(tipo,altura_max);
			return true;
		}else if(this.dato.equals("NOT")){
			return hijo1.cambiarArbolRandom(tipo, altura_max);
		}else if(this.dato.equals("AND") || this.dato.equals("OR")){
			if(r.nextBoolean())
				if(hijo1.cambiarArbolRandom(tipo, altura_max))
					return true;
			return hijo2.cambiarArbolRandom(tipo, altura_max);
		}else{ // dato == "IF"
			switch(r.nextInt(3)){
			case 0: 
				if(hijo1.cambiarArbolRandom(tipo, altura_max))
					return true;
			case 1:
				if(hijo2.cambiarArbolRandom(tipo, altura_max))
					return true;
			case 2:
				if(hijo3.cambiarArbolRandom(tipo, altura_max))
					return true;
			}
			return false;
		}
	}
	
	public String boolToBit(boolean x){
		if(x)
			return "1";
		else
			return "0";
	}
	
	public static void main(String[] args) {
		Arbol a = new Arbol(null, "IF");
		while(a.insertarDato(tipoLlenado.COMPLETO, 3)){}
		System.out.println(a.toString());
		a.cambiarArbolRandom(tipoLlenado.COMPLETO, 3);		 
		System.out.println(a.toString());
		a.cambiarArbolRandom(tipoLlenado.COMPLETO, 3);		 
		System.out.println(a.toString());
		a.cambiarArbolRandom(tipoLlenado.COMPLETO, 3);		 
		System.out.println(a.toString());
	}

	public int getNumHijo() {
		return numHijo;
	}

	public int getProfundidad(){
		if(datoEsTerminal())//this.dato.equals("D0") || this.dato.equals("D1") || this.dato.equals("D2") || this.dato.equals("D3") || this.dato.equals("A0") || this.dato.equals("A1"))
			return 1;
		else{
			int alt1,alt2,alt3;
			alt1 = alt2 = alt3 = 0;
			if(hijo1!=null)
				alt1 = hijo1.getProfundidad();
			if(hijo2!=null)
				alt2 = hijo2.getProfundidad();
			if(hijo3!=null)
				alt3 = hijo3.getProfundidad();
			if(alt1>alt2){
				if(alt1>alt3){
					return alt1+1;
				}else{
					if(alt3>alt2){
						return alt3+1;
					}else
						return alt2+1;
				}
			}else {
				if(alt2>alt3){
					return alt2+1;
				}else
					return alt3+1;
			}					
		}
	}
	
	public void setNumHijo(int numHijo) {
		this.numHijo = numHijo;
	}

//	public boolean esHoja() {
//		return (this.dato.equals("D0") || this.dato.equals("D1") || this.dato.equals("D2") || this.dato.equals("D3") || this.dato.equals("A0") || this.dato.equals("A1"));
//	}
	
}

